//===============================================================================
// Microsoft patterns & practices
// Web Client Software Factory
//-------------------------------------------------------------------------------
// Copyright (C) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//-------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================
using System;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using WCSFContrib.PageFlow.Services;
using System.Workflow.Runtime.Hosting;
using System.Collections.Generic;
using WCSFContrib.PageFlow.WorkflowFoundation.Properties;
using System.Globalization;

namespace WCSFContrib.PageFlow.WorkflowFoundation
{
	/// <summary>
    /// Implementation of an <see cref="IPageFlowFactory"/> that uses Windows Workflow Foundation.
	/// </summary>
    public class WorkflowFoundationPageFlowFactory : IPageFlowFactory, IDisposable
	{
		private INavigationService _navigationService;
		private WorkflowRuntime _runtime;
        private Dictionary<Type, WorkflowFoundationPageFlowDefinition> _workflowDefinitionsCache = new Dictionary<Type, WorkflowFoundationPageFlowDefinition>();
        
        private object _readerLockerDefs = new object();
        private object _writerLockerDefs = new object();

        /// <summary>
        /// Initilizes a new instance of the <see cref="WorkflowFoundationPageFlowFactory"/> class with a navigation service.
        /// </summary>
        /// <param name="navigationService">The navigation service to be used by the <see cref="IPageFlow"/> implementations.</param>
		public WorkflowFoundationPageFlowFactory(INavigationService navigationService)
		{
			_navigationService = navigationService;
		}

        /// <summary>
        /// Initilizes a new instance of the <see cref="WorkflowFoundationPageFlowFactory"/> class using the <see cref="NavigationService"/> as the default <see cref="INavigationService"/>.
        /// </summary>
		public WorkflowFoundationPageFlowFactory() : this(new NavigationService())
		{
		}

        /// <summary>
        /// Creates a <see cref="WorkflowInstance" /> with the specified <paramref name="pageFlowType"/>
        /// and returns a <see cref="WorkflowFoundationPageFlow"/> that wraps it.
        /// </summary>
        /// <param name="pageFlowType">The page flow type to create</param>
        /// <returns>A <see cref="WorkflowFoundationPageFlow"/>.</returns>
        /// <remarks>
        /// <para>A <see cref="WorkflowFoundationPageFlowDefinition"/> that contains metadata of the page flow is created using the <see cref="WorkflowInstance.GetWorkflowDefinition()">GetWorkflowDefinition</see> method. The definition for each <paramref name="pageFlowType"/> is cached because of performance issues.</para>
        /// <para>This method is intended to be called by the <see cref="WorkflowFoundationPageFlowProvider"/>.</para>
        /// </remarks>
		public IPageFlow GetPageFlow(Type pageFlowType)
		{
			InitializeWorkflowRuntime();

			WorkflowInstance instance = _runtime.CreateWorkflow(pageFlowType);
            instance.Unload();

            WorkflowFoundationPageFlowDefinition definition = GetOrCreateDefinition(pageFlowType, instance);

            return new WorkflowFoundationPageFlow(instance, _navigationService, definition);
		}
        
        /// <summary>
        /// Gets a <see cref="WorkflowInstance" /> with the specified <paramref name="instanceId"/> and returns a <see cref="WorkflowFoundationPageFlow"/> 
        /// that wraps it.
        /// </summary>
        /// <param name="pageFlowType">The page flow type to get the <see cref="WorkflowFoundationPageFlowDefinition"/>.</param>
        /// <param name="instanceId">The instance id of the <see cref="WorkflowInstance"/>.</param>
        /// <returns>A <see cref="WorkflowFoundationPageFlow"/>.</returns>
        /// <exception cref="PageFlowException">The <see cref="WorkflowInstance"/> that is trying to be deserialized changed its structure.</exception>
        public IPageFlow GetPageFlow(Type pageFlowType, Guid instanceId)
		{
            if (pageFlowType == null)
            {
                throw new ArgumentNullException("pageFlowType");
            }

			InitializeWorkflowRuntime();

            WorkflowInstance instance;
            try
            {
                instance = _runtime.GetWorkflow(instanceId);
            }
            catch (IndexOutOfRangeException ex)
            {
                throw new PageFlowException( String.Format(CultureInfo.CurrentCulture, Resources.PageFlowInstanceNotValid,
                                                            pageFlowType.AssemblyQualifiedName, instanceId), ex);
            }

            WorkflowFoundationPageFlowDefinition definition = GetOrCreateDefinition(pageFlowType, instance);

            return new WorkflowFoundationPageFlow(instance, _navigationService, definition);
		}

        private WorkflowFoundationPageFlowDefinition GetOrCreateDefinition(Type pageFlowType, WorkflowInstance instance)
        {
            WorkflowFoundationPageFlowDefinition cachedDefinition;
            lock (_readerLockerDefs)
            {
                if (!_workflowDefinitionsCache.TryGetValue(pageFlowType, out cachedDefinition))
                {
                    lock (_writerLockerDefs)
                    {
                        if (!_workflowDefinitionsCache.TryGetValue(pageFlowType, out cachedDefinition))
                        {
                            cachedDefinition = new WorkflowFoundationPageFlowDefinition((Activities.PageFlow)instance.GetWorkflowDefinition());
                            _workflowDefinitionsCache.Add(pageFlowType, cachedDefinition);
                        }
                    }
                }
            }
            return cachedDefinition;
        }
        
		private void InitializeWorkflowRuntime()
		{
			if (_runtime != null)
				return;

			_runtime = new WorkflowRuntime("pageFlow/hostingWorkflowRuntime");

			
			ExternalDataExchangeService externalDataExchangeService = _runtime.GetService<ExternalDataExchangeService>();
			AddServices(externalDataExchangeService);

			_runtime.StartRuntime();
		}

		private static void AddServices(ExternalDataExchangeService serviceContainer)
		{
			DataExchangeService dataExchangeService = new DataExchangeService();
			dataExchangeService.DataReturned += new EventHandler<DataReturnedEventArgs>(WorkflowDataAdapter.Instance.HandleDataReturned);
			dataExchangeService.KeysReturned += new EventHandler<KeysReturnedEventArgs>(WorkflowDataAdapter.Instance.HandleKeyReturned);
			serviceContainer.AddService(dataExchangeService);
		}

        /// <summary>
        /// Disposing method as used in the Dispose pattern.
        /// </summary>
        /// <param name="disposing">True if we are called during Dispose. False if we are called from finalizer</param>        
        protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (_runtime != null)
				{
					_runtime.Dispose();
				}
			}
		}

        /// <summary>
        /// Disposes the underlying <see cref="WorkflowRuntime"/>.
        /// </summary>		
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}
	}
}
